package com.thiruvathukal.examples.dom.traversal;

/*
 * XML code examples by George K. Thiruvathukal. Unless noted otherwise
 * this code is covered by GPLv2. We are still working on the licensing
 * text and will update all code and documentation shortly.
 */

import java.util.Vector;

import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Entity;
import org.w3c.dom.EntityReference;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Notation;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;

public class DOMTraversal {

	private Node top;
	private Vector observers;

	public DOMTraversal(Node top) {
		this.top = top;
		this.observers = new Vector();
	}

	public void addVisitor(DOMNodeEvents visitor) {
		observers.addElement(visitor);
	}

	public void visit(Object userDefinedObject) {
		visitInDocumentOrder(top, userDefinedObject);
	}

	private void notifyVisitors(Node node, Object userData, boolean start) {
		if (start) {
			for (int i = 0; i < observers.size(); i++) {
				DOMNodeEvents item = (DOMNodeEvents) observers.elementAt(i);
				if (node instanceof Attr)
					item.sawAttr((Attr) node, userData);
				else if (node instanceof CDATASection)
					item.sawCDATASection((CDATASection) node, userData);
				else if (node instanceof CharacterData)
					item.sawCharacterData((CharacterData) node, userData);
				else if (node instanceof Document)
					item.startDocument((Document) node, userData);
				else if (node instanceof DocumentFragment)
					item.sawDocumentFragment((DocumentFragment) node, userData);
				else if (node instanceof DocumentType)
					item.sawDocumentType((DocumentType) node, userData);
				else if (node instanceof Element)
					item.startElement((Element) node, userData);
				else if (node instanceof Entity)
					item.sawEntity((Entity) node, userData);
				else if (node instanceof EntityReference)
					item.startEntityReference((EntityReference) node, userData);
				else if (node instanceof Notation)
					item.sawNotation((Notation) node, userData);
				else if (node instanceof ProcessingInstruction)
					item.sawProcessingInstruction((ProcessingInstruction) node,
							userData);
				else if (node instanceof Text)
					item.sawText((Text) node, userData);
			}
		} else {
			for (int i = 0; i < observers.size(); i++) {
				DOMNodeEvents item = (DOMNodeEvents) observers.elementAt(i);
				if (node instanceof Document)
					item.endDocument((Document) node, userData);
				else if (node instanceof Element)
					item.endElement((Element) node, userData);
				else if (node instanceof EntityReference)
					item.endEntityReference((EntityReference) node, userData);
			}
		}
	}

	private void visitInDocumentOrder(Node node, Object udo) {
		if (node == null)
			return;
		notifyVisitors(node, udo, true);

		switch (node.getNodeType()) {
		case Node.DOCUMENT_NODE: {
			Document document = (Document) node;
			visitInDocumentOrder(document.getDocumentElement(), udo);
			notifyVisitors(node, udo, false);
			break;
		}

		case Node.ELEMENT_NODE: {
			NamedNodeMap attrs = node.getAttributes();
			for (int attrNo = 0; attrNo < attrs.getLength(); attrNo++) {
				visitInDocumentOrder(attrs.item(attrNo), udo);
			}
			// NOTE: This case falls through because Element and
			// EntityReferenceNode both may have descendants.
		}

		case Node.ENTITY_REFERENCE_NODE: {
			Node child = node.getFirstChild();
			while (child != null) {
				visitInDocumentOrder(child, udo);
				child = child.getNextSibling();
			}
			// TODO: Add events for start/end Entity Reference
			notifyVisitors(node, udo, false);
			break;
		}
		}
	}
}
